12.3 编译
编译并不仅仅是执行“go build”命令,还有一些须额外注意的内容。
如习惯使用GDB这类调试器,建议编译时添加-gcflags”-N-l”参数阻止优化和内联,否则调试时会有各种“找不到”的情况。
package main
func test(x*int) { println(*x) }
func main() { x:=0x100 test(&x) }
输出:
$go build-gcflags”-N-l-m”
./test.go:3:test x does not escape ./test.go:9:main&x does not escape
$go tool objdump-s”main.main”test
TEXT main.main(SB)test.go test.go:7 SUBQ0x100,0x8(SP) test.go:9 LEAQ 0x8(SP),BX test.go:9 MOVQ BX,0(SP) test.go:9 CALL main.test(SB) test.go:10 ADDQ$0x10,SP test.go:10 RET
$ls-lh test -rwxr-xr-x 1 1000 1000 1.1M Mar 28 2016 test
而当发布时,参数-ldfalgs”-w-s”会让链接器剔除符号表和调试信息,除能减小可执行文件大小外,还可稍稍增加反汇编的难度。
$go build-gcflags”-m” -ldflags”-w-s”
./test.go:3:can inline test ./test.go:7:can inline main ./test.go:9:inlining call to test ./test.go:3:test x does not escape ./test.go:9:main&x does not escape
$go tool objdump-s”main.main”test objdump:disassemble test:no symbol section
$ls-lh test -rwxr-xr-x 1 1000 1000 720K Mar 28 2016 test
还可借助更专业的工具,对可执行文件进行减肥。
$upx-9 test
Ultimate Packer for eXecutables
Copyright(C)1996-2013
UPX 3.91 Markus Oberhumer,Laszlo Molnar&John Reiser Sep 30th 2013
File size Ratio Format Name
737024→ 244876 33.22% linux/ElfAMD test
Packed 1 file.
交叉编译
所谓交叉编译(cross compile),是指在一个平台下编译出其他平台所需的可执行文件。这对于UNIX-like开发人员很重要,因为我们习惯使用Mac或其他桌面环境。
自Go实现自举后,交叉编译变得更方便。只须使用GOOS、GOARCH环境变量指定目标平台和架构就行。
$go env GOOS darwin
$go build&&file test test:Mach-O 64-bit executable x86_64
$GOOS=linux go build&&file test test:ELF 64-bit LSB executable,x86-64,version 1(SYSV),statically linked,not stripped
$GOOS=windows GOARCH=386 go build&&file test.exe test.exe:PE32 executable for MS Windows(console)Intel 80386 32-bit
建议用go install命令为目标平台预编译好标准库,避免go build每次都须完整编译。
GOOS=linux go install cmd # 生成目标平台工具链,可选
注意:交叉编译不支持CGO。
条件编译
除在代码中用runtime.GOOS进行判断外,编译器本身就支持文件级别的条件编译。虽说没有C预编译指令那么方便,但是基于文件的组织方式更便于维护。
方法一:将平台和架构信息添加到主文件名尾部。
main.go
package main
func main() { hello() }
hello_darwin.go
package main
func hello() { println(“hello,mac.“) }
hello_linux.go
package main
func hello() { println(“hello,linux.“) }
使用GOOS交叉编译,看看具体使用哪个文件。
$GOOS=darwin go build-x compile… -pack./hello_darwin.go./main.go
$GOOS=linux go build-x compile… -pack./hello_linux.go./main.go
编译器会选择对应的源码文件进行编译。在标准库里可以看到很多类似的文件名。
$ls/usr/local/go/src/runtime/sys_*
sys_arm.go sys_darwin_arm64.s sys_linux_386.s sys_linux_ppc64x.s
sys_arm64.go sys_dragonfly_amd64.s sys_linux_amd64.s sys_mips64x.go
sys_darwin_386.s sys_freebsd_386.s sys_linux_arm.s sys_nacl_386.s
sys_darwin_amd64.s sys_freebsd_amd64.s sys_linux_arm64.s sys_nacl_amd64p32.s
sys_darwin_arm.s sys_freebsd_arm.s sys_linux_mips64x.s sys_nacl_arm.s
文件名中除GOOS外,还可以加上GOARCH,或任选其一。
方法二:使用build编译指令。
与用文件名区分多版本类似,build编译指令告知编译器:当前源码文件只能用于指定环境。它一样可用来区分多版本,且控制指令更加丰富和灵活。
a.go
// +build windows ⇐------ 必须有空行 package main
func hello() { println(“hello,windows.“) }
b.go
// +build linux darwin
package main
func hello() { println(“hello,unix.“) }
可添加多条build指令,表示多个AND条件。在单一指令里,空格表示OR条件,逗号表示AND,感叹号表示NOT。
// +build linux darwin // +build 386,!cgo
相当于:
(linux OR darwin)AND(386 AND(NOT cgo))
除GOOS、GOARCH外,可用条件还有编译器、版本号等。
// +build ignore // +build gccgo // +build go1.5
更多详细信息,请阅读标准库go/build文档。
方法三:使用自定义tag指令。
除预定义build指令外,也可通过命令行tags参数传递自定义指令。
main.go
package main
func main() { hello() }
debug.go
// +build!release
package main
func hello() { println(“debug version.“) }
release.go
// +build!release
package main
func hello() { println(“debug version.“) }
log.go
// +build log
package main
func init() { println(“logging…“) }
如有多个自定义条件,用空格分开。
$go build&& ./test debug version.
$go build-tags”release log” && ./test logging… release version.
预处理
简单点说,就是用go generate命令扫描源码文件,找出所有“go:generate”注释,提取其中的命令并执行。
- 命令必须放在.go源文件中。
- 命令必须以“//go:generate”开头(双斜线后不能有空格)。
- 每个文件可有多条generate命令。
- 命令支持环境变量。
- 必须显式执行go generate命令。
- 按文件名顺序提取命令并执行。
- 串行执行,出错后终止后续命令的执行。
这种设计的初衷是为包开发者准备的,可用其完成一些自动处理命令。比如在发布时,清理掉一些包用户不会使用的测试代码。除此之外,还可用来完成基于模板生成代码(类似泛型功能),或将资源文件转换为源码(.resx嵌入资源)等工作。
a.go
//go:generate echo$GOPATH //go:generate ls-lh
package main
func hello() { println(“hello world!“) }
b.go
//go:generate uname-a
package main
func init() { }
支持以下命令行参数:
参数 说明 示例 ------------------+-------------------------------------+---------------- -v 显示处理的包及文件名 -x 显示准备执行的命令 -n 仅显示命令,但不执行
$go generate-n
echo/home/yuhen/go/test ls-lh uname-a
可为当前文件中的命令定义别名(仅当前文件有效),以便多次重复使用。
//go:generate-command LX ls-l //go:generate LX/var //go:generate LX/usr